package com.ffs.game.utils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * @author ffs80
 * @date 2022/4/4 19:43
 */
public class ClassUtil {


    /**
     * 根据注解查找类
     *
     * @param packageName 包名
     * @param clazz       注解类
     */
    public static Set<Class<?>> findClassByAnnotation(String packageName, Class<? extends Annotation> clazz) {
        Set<Class<?>> classByPackage = findClassByPackage(packageName);
        return classByPackage.stream().filter(p -> p.getAnnotation(clazz) != null).collect(Collectors.toSet());
    }

    public static Set<Class<?>> findClassByPackage(String packageName) {
        Set<Class<?>> allClass = new LinkedHashSet<>();
        String packageDir = packageName.replace(".", "/");
        try {
            Enumeration<URL> urlEnumeration = ClassUtil.class.getClassLoader().getResources(packageDir);
            while (urlEnumeration.hasMoreElements()) {
                URL url = urlEnumeration.nextElement();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    allClass.addAll(findClassFromDir(packageName, filePath, true));
                } else if ("jar".equals(protocol)) {
                    JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                    allClass.addAll(findClassFromJar(jarFile, packageDir));
                }
            }
        } catch (Exception e) {

        }
        return allClass;
    }

    /**
     * 获取文件夹下的所有类
     *
     * @param packageName 包名
     * @param filePath    文件夹路径
     * @param recursive   是否递归寻找
     */
    private static Set<Class<?>> findClassFromDir(String packageName, String filePath, boolean recursive) {

        File dir = new File(filePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return Collections.EMPTY_SET;
        }

        Set<Class<?>> ret = new LinkedHashSet<>();

        File[] files = dir.listFiles(file -> (recursive && file.isDirectory()) || file.getName().endsWith(".class"));

        for (File file : files) {
            if (file.isDirectory()) {
                ret.addAll(findClassFromDir(packageName + "." + file.getName(), file.getAbsolutePath(), recursive));
                continue;
            } else {
                try {
                    String className = file.getName().substring(0, file.getName().length() - 6);
                    Class<?> clazz = Class.forName(packageName + "." + className, false, ClassUtil.class.getClassLoader());
                    ret.add(clazz);
                } catch (Exception e) {

                }

            }
        }
        return ret;
    }

    private static Set<Class<?>> findClassFromJar(JarFile jar, String packageDir) {
        Set<Class<?>> ret = new LinkedHashSet<>();
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            if (jarEntry.isDirectory()) {
                continue;
            }

            String name = jar.getName();
            if (!name.startsWith(packageDir) || !name.endsWith(".class")) {
                continue;
            }
            name = name.replaceAll("/", ".");
            name = name.substring(0, name.length() - 6);

            try {
                Class<?> clazz = Class.forName(name, false, ClassUtil.class.getClassLoader());
                ret.add(clazz);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        return ret;
    }
}
